import tkinter as tk
from tkinter import ttk, messagebox
import threading
import time
import os
import sys
import logging
from datetime import datetime
import queue

import requests
import urllib3
from frpc import Frpc
import pystray
from PIL import Image, ImageDraw

# 禁用 urllib3 的 InsecureRequestWarning 警告
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# pyinstaller --onefile --windowed --icon=frp.ico --add-data "frp.ico;." frp_gui.py


# 配置日志
def setup_logging(log_queue):
    """配置统一的日志系统"""
    log_dir = "logs"
    if not os.path.exists(log_dir):
        os.makedirs(log_dir)

    log_file = os.path.join(log_dir, f"frp_gui_{datetime.now().strftime('%Y%m%d')}.log")

    # 创建日志格式化器
    formatter = logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")

    # 创建文件处理器
    file_handler = logging.FileHandler(log_file, encoding="utf-8")
    file_handler.setFormatter(formatter)

    # 创建队列处理器
    class QueueHandler(logging.Handler):
        def __init__(self, log_queue):
            super().__init__()
            self.log_queue = log_queue

        def emit(self, record):
            try:
                msg = self.format(record)
                self.log_queue.put(msg + "\n")
            except Exception:
                self.handleError(record)

    queue_handler = QueueHandler(log_queue)
    queue_handler.setFormatter(formatter)

    # 配置根日志记录器
    root_logger = logging.getLogger()
    root_logger.setLevel(logging.INFO)

    # 移除所有现有的处理器
    for handler in root_logger.handlers[:]:
        root_logger.removeHandler(handler)

    # 添加新的处理器
    root_logger.addHandler(file_handler)
    root_logger.addHandler(queue_handler)

    return root_logger


class LoadingAnimation:
    def __init__(self, parent):
        self.parent = parent
        self.label = ttk.Label(parent, text="", font=("微软雅黑", 10))
        self.dots = 0
        self.is_running = False

    def start(self, text="处理中"):
        self.is_running = True
        self.text = text
        self.label.pack(side=tk.LEFT, padx=5)
        self.animate()

    def stop(self):
        self.is_running = False
        self.label.pack_forget()

    def animate(self):
        if not self.is_running:
            return

        self.dots = (self.dots + 1) % 4
        dots = "." * self.dots
        self.label.configure(text=f"{self.text}{dots}")
        self.parent.after(1000, self.animate)


class FrpGUI:
    def __init__(self):
        self.root = tk.Tk()
        self.root.title("FRP 客户端管理器")
        # 移除固定大小限制
        self.root.resizable(True, True)  # 允许调整大小

        # 设置窗口居中
        self.center_window()

        # 设置窗口图标
        try:
            # 获取图标资源文件路径
            if getattr(sys, "frozen", False):
                # 如果是打包后的可执行文件
                application_path = os.path.dirname(sys.executable)
                icon_path = os.path.join(application_path, "frp.ico")
                if not os.path.exists(icon_path):
                    # 尝试从临时目录获取资源
                    base_path = getattr(sys, "_MEIPASS", application_path)
                    icon_path = os.path.join(base_path, "frp.ico")
            else:
                # 如果是直接运行脚本
                icon_path = "frp.ico"

            self.root.iconbitmap(icon_path)
            self.icon_path = icon_path
        except Exception as e:
            logging.error(f"加载图标失败: {str(e)}")
            self.icon_path = None
        # 初始化变量
        self.frpc = None
        self.frpc_thread = None
        self.is_running = False
        self.client_count = 0
        self.seig_status = "未知"
        self.log_queue = queue.Queue()
        self.tray_icon = None
        self.is_minimized = False
        self.background_mode = False  # 添加后台模式标志
        self.background_frpc = None  # 后台模式下的Frpc实例

        # 配置日志
        self.logger = setup_logging(self.log_queue)

        # 创建界面
        self.create_widgets()

        # 创建加载动画
        self.loading = LoadingAnimation(self.control_frame)

        # 启动日志处理线程
        self.log_thread = threading.Thread(target=self.process_log_queue, daemon=True)
        self.log_thread.start()

        # 设置窗口关闭事件
        self.root.protocol("WM_DELETE_WINDOW", self.on_closing)

        # 设置样式
        self.setup_styles()

        # 创建系统托盘图标
        self.create_tray_icon()

    def center_window(self):
        """将窗口居中显示"""
        # 先让窗口自然布局
        self.root.update_idletasks()

        # 获取屏幕尺寸
        screen_width = self.root.winfo_screenwidth()
        screen_height = self.root.winfo_screenheight()

        # 获取窗口尺寸
        width = min(1000, screen_width * 0.8)  # 最大宽度为1000或屏幕宽度的80%
        height = min(800, screen_height * 0.8)  # 最大高度为800或屏幕高度的80%

        # 计算位置
        x = (screen_width - width) // 2
        y = (screen_height - height) // 2

        # 设置窗口大小和位置
        self.root.geometry(f"{int(width)}x{int(height)}+{x}+{y}")

    def setup_styles(self):
        """设置界面样式"""
        style = ttk.Style()
        style.configure("TButton", padding=6, relief="flat", background="#2196F3")
        style.configure("TFrame", background="#f0f0f0")
        style.configure("TLabel", background="#f0f0f0", font=("微软雅黑", 10))

        # 设置文本框样式
        self.log_text.configure(
            font=("Consolas", 10), background="#ffffff", foreground="#333333"
        )

    def create_widgets(self):
        """创建界面组件"""
        # 主框架
        main_frame = ttk.Frame(self.root, padding="10")
        main_frame.pack(fill=tk.BOTH, expand=True)

        # 说明文字区域
        description_frame = ttk.Frame(main_frame)
        description_frame.pack(fill=tk.X, pady=(0, 10))

        description_text = (
            "【程序用途】\n"
            "本程序是SEIG教务系统的公网接入助手，在非 选课/期末 时段（SEIG仅限校园内网访问时），为您搭建专属数据通道。\n"
            "当您在 网站/小程序 下需要强制更新课表、成绩等教务数据时， 网站/小程序 将通过本程序安全转接请求至校内SEIG服务器。\n\n"
            "【系统运行机制】\n"
            "▶ 双通道智能切换：\n"
            "   - 选课/期末季（SEIG开放公网）： 网站/小程序 直连无需本程序\n"
            "   - 日常时段（SEIG仅限内网）：通过本程序建立安全隧道访问\n"
            "▶ 数据缓存说明：\n"
            "    网站/小程序 本地或服务器会保存课表等基础数据方便查阅，点击「强制更新」时才触发实时数据请求\n\n"
            "【您的两种使用模式】\n"
            "🔹 个人模式（推荐日常使用）：\n"
            "   启动FRP后获得数据后直接关闭程序，下次需要强制更新时重新启动\n"
            "🔹 共享模式（后台运行）：\n"
            "   最小化到托盘保持运行，系统将：\n"
            "   ✓ 为其他用户提供中转服务\n"
            "   ✓ 智能分配访问流量（负载均衡）\n"
            "   ✓ 自动维护连接稳定性\n\n"
            "【技术优势】\n"
            "任何用户的账号密码等敏感信息全程受SSL保护，并且不会在任意环节（包括本程序）泄露。\n"
            "负载均衡机制让每台中转客户端仅承担微小流量压力，全程不会影响您的正常网络使用。\n\n"
            "提示：本程序中转需要连接公网（校园网），程序托盘化运行时，内存占用约45MB，如同保持开启一个记事本的资源消耗。您的每次共享都在帮助缩短全体用户的平均等待时长！"
        )

        description_label = ttk.Label(
            description_frame,
            text=description_text,
            font=("微软雅黑", 9),
            wraplength=780,
            # justify=tk.CENTER,
        )
        description_label.pack(pady=5)

        # 顶部控制区域
        self.control_frame = ttk.Frame(main_frame)
        self.control_frame.pack(fill=tk.X, pady=(0, 10))

        # 启动/停止按钮
        self.toggle_button = ttk.Button(
            self.control_frame, text="启动 FRP", command=self.toggle_frpc, width=15
        )
        self.toggle_button.pack(side=tk.LEFT, padx=5)

        # 刷新按钮
        self.refresh_button = ttk.Button(
            self.control_frame, text="刷新状态", command=self.refresh_status, width=15
        )
        self.refresh_button.pack(side=tk.LEFT, padx=5)

        # 客户端数量显示
        self.client_count_label = ttk.Label(
            self.control_frame, text="当前可用客户端: 0", font=("微软雅黑", 10, "bold")
        )
        self.client_count_label.pack(side=tk.LEFT, padx=20)

        # SEIG状态显示
        self.seig_status_label = ttk.Label(
            self.control_frame, text="当前SEIG状态: 未知", font=("微软雅黑", 10, "bold")
        )
        self.seig_status_label.pack(side=tk.LEFT, padx=20)

        # 日志文本框
        log_frame = ttk.Frame(main_frame)
        log_frame.pack(fill=tk.BOTH, expand=True)

        # 创建文本框和滚动条
        self.log_text = tk.Text(log_frame, wrap=tk.WORD, height=15, state="disabled")
        scrollbar = ttk.Scrollbar(
            log_frame, orient=tk.VERTICAL, command=self.log_text.yview
        )
        self.log_text.configure(yscrollcommand=scrollbar.set)

        # 放置文本框和滚动条
        self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

        # 添加初始日志
        self.log("启动！")

    def set_controls_state(self, state):
        """设置控件状态"""
        self.toggle_button.configure(state=state)
        self.refresh_button.configure(state=state)

    def get_seig_status(self):
        """获取SEIG状态"""
        try:
            response = requests.get(
                "http://172.16.3.70:7001/sise/", timeout=2, verify=False
            )
            return "正常" if response.status_code == 200 else "异常"
        except Exception as e:
            logging.error(f"检查SEIG状态时出错: {str(e)}")
            return "异常"

    def get_client_count(self):
        """获取客户端数量"""
        try:
            response = requests.get(
                "https://www.evant.fun/api/course/get_seig_frp_status",
                # "http://127.0.0.1:9676/api/course/get_seig_frp_status",
                timeout=2,
                verify=False,
            )
            return response.json()["client_count"]
        except Exception as e:
            logging.error(f"获取客户端数量时出错: {str(e)}")
            return 0

    def refresh_status(self):
        """刷新状态信息"""

        def do_refresh():
            try:
                self.set_controls_state("disabled")
                self.loading.start("刷新状态中")

                # 更新客户端数量（固定为2）
                self.client_count = self.get_client_count()
                self.client_count_label.configure(
                    text=f"当前可用客户端: {self.client_count}"
                )

                # 更新SEIG状态
                self.seig_status = self.get_seig_status()
                self.seig_status_label.configure(
                    text=f"当前SEIG状态: {self.seig_status}"
                )

                # 记录日志
                self.log(
                    f"当前客户端数量: {self.client_count}, 当前SEIG状态: {self.seig_status}"
                )

            finally:
                self.loading.stop()
                self.set_controls_state("normal")

        # 在新线程中执行刷新操作
        threading.Thread(target=do_refresh, daemon=True).start()

    def log(self, message):
        """添加日志"""
        self.logger.info(message)

    def process_log_queue(self):
        """处理日志队列中的消息"""
        while True:
            try:
                # 使用阻塞方式获取消息，超时为1秒
                try:
                    message = self.log_queue.get(block=True, timeout=1)
                    self.log_text.configure(state="normal")
                    self.log_text.insert(tk.END, message)
                    self.log_text.see(tk.END)
                    self.log_text.configure(state="disabled")
                except queue.Empty:
                    # 超时，继续循环
                    pass
            except Exception as e:
                logging.error(f"处理日志队列时出错: {str(e)}")
                time.sleep(1)  # 出错时增加休眠时间

    def toggle_frpc(self):
        """切换FRP客户端的启动/停止状态"""
        self.seig_status = self.get_seig_status()
        self.seig_status_label.configure(text=f"当前SEIG状态: {self.seig_status}")
        if not self.is_running:
            self.start_frpc()
        else:
            self.stop_frpc()

    def start_frpc(self):
        """启动FRP客户端"""
        # 先检查 SEIG 状态
        if self.seig_status != "正常":
            self.log(f"SEIG 状态{self.seig_status}，无法启动 FRP 客户端")
            return

        def do_start():
            try:
                self.set_controls_state("disabled")
                self.loading.start("启动中")

                # ============= 配置参数 =============
                # 远程服务器配置
                remote_host = "xxxxx"  # FRP服务器地址
                remote_port = xxxxx  # FRP服务器端口

                # 目标服务器配置
                target_host = "172.16.3.70"  # 目标服务器地址
                target_port = 7001  # 目标服务器端口
                # ===================================

                # 创建FRP客户端实例
                self.frpc = Frpc(remote_host, remote_port, target_host, target_port)

                # 在新线程中运行FRP客户端
                self.frpc_thread = threading.Thread(target=self.frpc.run, daemon=True)
                self.frpc_thread.start()

                self.is_running = True
                self.toggle_button.configure(text="停止 FRP")
                self.log("FRP 客户端已启动")
                self.refresh_status()

                time.sleep(1)  # 模拟操作延迟
            except Exception as e:
                self.log(f"启动 FRP 客户端失败: {str(e)}")
                messagebox.showerror("错误", f"启动 FRP 客户端失败: {str(e)}")
            finally:
                self.loading.stop()
                self.set_controls_state("normal")

        # 在新线程中执行启动操作
        threading.Thread(target=do_start, daemon=True).start()

    def stop_frpc(self, callback=None):
        """
        停止FRP客户端

        参数:
            callback: 当停止完成时要调用的回调函数
        """
        # 创建一个停止完成事件
        stop_completed = threading.Event()

        def do_stop():
            try:
                self.set_controls_state("disabled")
                self.loading.start("停止中")

                if self.frpc:
                    # 调用stop方法停止FRP客户端
                    self.frpc.stop()

                    # 等待线程结束
                    if self.frpc_thread:
                        self.frpc_thread.join(timeout=5)

                    self.log("FRP 客户端已停止 可用客户端数量刷新有1分钟延迟")
            except Exception as e:
                self.log(f"停止 FRP 客户端时出错: {str(e)}")
            finally:
                self.frpc = None
                self.frpc_thread = None
                self.is_running = False
                self.toggle_button.configure(text="启动 FRP")
                # self.refresh_status()
                self.loading.stop()
                self.set_controls_state("normal")

                # 设置停止完成事件
                stop_completed.set()

                # 如果有回调函数，调用它
                if callback:
                    callback()

        # 在新线程中执行停止操作
        threading.Thread(target=do_stop, daemon=True).start()

        # 返回停止完成事件
        return stop_completed

    def create_tray_icon(self):
        """创建系统托盘图标"""
        if self.icon_path and os.path.exists(self.icon_path):
            image = Image.open(self.icon_path)
        else:
            # 创建一个默认图标
            image = Image.new("RGB", (64, 64), color="blue")
            draw = ImageDraw.Draw(image)
            draw.rectangle([0, 0, 63, 63], outline="white", width=2)
            draw.text((20, 20), "FRP", fill="white")

        menu = (
            pystray.MenuItem("显示主窗口", self.show_window),
            pystray.MenuItem("退出", self.quit_application),
        )

        self.tray_icon = pystray.Icon("frp", image, "FRP 客户端管理器", menu)
        threading.Thread(target=self.tray_icon.run, daemon=True).start()

    def start_background_frpc(self):
        """在后台模式下启动Frpc"""
        try:
            # ============= 配置参数 =============
            # 远程服务器配置
            remote_host = "150.138.78.137"  # FRP服务器地址
            remote_port = 9450  # FRP服务器端口

            # 目标服务器配置
            target_host = "172.16.3.70"  # 目标服务器地址
            target_port = 7001  # 目标服务器端口
            # ===================================

            # 创建并启动Frpc实例
            self.background_frpc = Frpc(
                remote_host, remote_port, target_host, target_port
            )

            # 在新线程中运行
            self.background_thread = threading.Thread(
                target=self.background_frpc.run, daemon=True
            )
            self.background_thread.start()

            self.background_mode = True
            self.log("后台模式已启动")
        except Exception as e:
            self.log(f"启动后台模式失败: {str(e)}")

    def stop_background_frpc(self):
        """停止后台模式下的Frpc"""
        if self.background_frpc:
            try:
                self.background_frpc.stop()

                # 等待线程结束
                if self.background_thread:
                    self.background_thread.join(timeout=5)

                self.background_frpc = None
                self.background_thread = None
                self.background_mode = False
                self.log("后台模式已停止")
            except Exception as e:
                self.log(f"停止后台模式时出错: {str(e)}")

    def show_window(self):
        """显示主窗口"""
        # 如果在后台模式，先停止后台服务
        if self.background_mode:
            self.stop_background_frpc()

        # 显示主窗口
        self.root.deiconify()
        self.root.state("normal")
        self.root.lift()
        self.root.focus_force()
        self.is_minimized = False

    def quit_application(self):
        """退出应用程序"""
        if self.is_running or self.background_mode:
            if messagebox.askyesno("确认", "FRP 客户端正在运行，确定要退出吗？"):
                # 如果在后台模式，停止后台服务
                if self.background_mode:
                    self.stop_background_frpc()

                # 停止GUI中的FRP客户端并设置回调
                if self.is_running:

                    def on_stop_completed():
                        if self.tray_icon:
                            self.tray_icon.stop()
                        self.root.quit()

                    # 停止FRP并等待完成
                    stop_event = self.stop_frpc(callback=on_stop_completed)
                else:
                    # 仅有后台模式在运行，已停止
                    if self.tray_icon:
                        self.tray_icon.stop()
                    self.root.quit()
            else:
                # 用户取消了操作
                pass
        else:
            if self.tray_icon:
                self.tray_icon.stop()
            self.root.quit()

    def on_closing(self):
        """处理窗口关闭事件"""
        if self.is_running:
            response = messagebox.askyesnocancel(
                "确认",
                "FRP 客户端正在运行，请选择操作：\n"
                " 是 - 最小化到系统托盘（后台运行）\n"
                " 否 - 停止并退出\n"
                " 取消 - 取消操作",
            )
            if response is None:  # 取消
                return
            elif response:  # 是 - 最小化到托盘并进入后台模式
                # 先停止GUI中的FRP客户端
                def on_stop_completed():
                    # 启动后台模式
                    self.start_background_frpc()
                    # 隐藏主窗口
                    self.root.withdraw()
                    self.is_minimized = True

                    # 停止日志处理线程
                    self.log_thread.join()

                # 如果当前有FRP在GUI中运行，先停止它
                if self.is_running:
                    logging.info("当前有FRP在GUI中运行，正在重新启动....")
                    time.sleep(1)
                    # 停止FRP
                    stop_event = self.stop_frpc(callback=on_stop_completed)
                else:
                    # 直接启动后台模式并隐藏窗口
                    self.start_background_frpc()
                    self.root.withdraw()
                    self.is_minimized = True
            else:  # 否 - 停止并退出
                # 停止FRP客户端并设置回调函数
                def on_stop_completed():
                    if self.tray_icon:
                        self.tray_icon.stop()
                    self.root.destroy()

                # 停止FRP并等待完成
                stop_event = self.stop_frpc(callback=on_stop_completed)

                # 不在这里直接调用destroy，而是在回调中调用
        else:
            if self.tray_icon:
                self.tray_icon.stop()
            self.root.destroy()

    def run(self):
        """运行GUI程序"""
        self.root.mainloop()


if __name__ == "__main__":
    app = FrpGUI()
    app.run()
